"""
文件管理服务
"""

import hashlib
import os
import shutil
from pathlib import Path
from typing import  Optional, List, Dict, Any, BinaryIO
from typing import  UploadFile
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, or_, desc, func

from models.file import File
from config import settings
from utils.logger import get_logger

logger = get_logger(__name__)


class FileService:
    """文件管理服务"""

    def __init__(self):
        self.upload_dir = Path(settings.upload_dir)
        self.upload_dir.mkdir(parents=True, exist_ok=True)

    def _calculate_file_hash(self, file_content: bytes) -> str:
        """计算文件哈希值"""
        return hashlib.sha256(file_content).hexdigest()

    def _get_safe_filename(self, filename: str) -> str:
        """获取安全的文件名"""
        # 移除路径分隔符和其他危险字符
        safe_chars = "-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        safe_filename = "".join(c for c in filename if c in safe_chars)

        # 限制文件名长度
        if len(safe_filename) > 255:
            name, ext = os.path.splitext(safe_filename)
            safe_filename = name[:255-len(ext)] + ext

        return safe_filename or "unnamed_file"

    def _get_unique_filepath(self, filename: str) -> Path:
        """获取唯一的文件路径"""
        safe_filename = self._get_safe_filename(filename)
        filepath = self.upload_dir / safe_filename

        # 如果文件已存在，添加数字后缀
        counter = 1
        original_filepath = filepath
        while filepath.exists():
            name, ext = os.path.splitext(original_filepath.name)
            filepath = self.upload_dir / f"{name}_{counter}{ext}"
            counter += 1

        return filepath

    async def save_uploaded_file(
        self,
        file: UploadFile,
        target_path: Optional[str] = None,
        description: Optional[str] = None,
        created_by: Optional[str] = None
    ) -> File:
        """保存上传的文件"""
        try:
            # 读取文件内容
            file_content = await file.read()

            # 验证文件大小
            if len(file_content) > settings.max_file_size:
                raise ValueError(f"文件大小超过限制 ({settings.max_file_size} bytes)")

            # 计算文件哈希
            file_hash = self._calculate_file_hash(file_content)

            # 检查是否已存在相同文件
            async with get_db_session() as db:
                existing_file = await self.get_file_by_hash(file_hash)
                if existing_file:
                    logger.info("文件已存在", file_hash=file_hash, existing_file_id=existing_file.id)
                    return existing_file

            # 生成唯一文件路径
            filepath = self._get_unique_filepath(file.filename)

            # 保存文件到磁盘
            with open(filepath, "wb") as f:
                f.write(file_content)

            # 获取文件内容类型
            content_type = file.content_type or "application/octet-stream"

            # 保存文件记录到数据库
            async with get_db_session() as db:
                db_file = File(
                    name=self._get_safe_filename(file.filename),
                    original_name=file.filename,
                    file_path=str(filepath),
                    target_path=target_path or f"/{self._get_safe_filename(file.filename)}",
                    content_type=content_type,
                    file_size=len(file_content),
                    file_hash=file_hash,
                    description=description,
                    created_by=created_by
                )

                db.add(db_file)
                await db.commit()
                await db.refresh(db_file)

                logger.info(
                    "文件上传成功",
                    file_id=db_file.id,
                    filename=file.filename,
                    size=len(file_content),
                    hash=file_hash
                )

                return db_file

        except Exception as e:
            # 如果保存失败，清理已创建的文件
            if 'filepath' in locals() and filepath.exists():
                filepath.unlink()

            logger.error("文件上传失败", filename=file.filename, error=str(e))
            raise

    async def get_file_by_id(self, file_id: int) -> Optional[File]:
        """根据ID获取文件"""
        try:
            async with get_db_session() as db:
                file = await db.get(File, file_id)
                if file:
                    logger.debug("获取文件成功", file_id=file_id)
                else:
                    logger.warning("文件不存在", file_id=file_id)
                return file
        except Exception as e:
            logger.error("获取文件失败", file_id=file_id, error=str(e))
            raise

    async def get_file_by_hash(self, file_hash: str) -> Optional[File]:
        """根据哈希值获取文件"""
        try:
            async with get_db_session() as db:
                stmt = select(File).where(File.file_hash == file_hash)
                result = await db.execute(stmt)
                file = result.scalar_one_or_none()
                return file
        except Exception as e:
            logger.error("根据哈希获取文件失败", file_hash=file_hash, error=str(e))
            raise

    async def get_files(
        self,
        page: int = 1,
        size: int = 10,
        search: Optional[str] = None
    ) -> Dict[str, Any]:
        """获取文件列表"""
        try:
            async with get_db_session() as db:
                # 构建查询条件
                conditions = []

                if search:
                    conditions.append(
                        or_(
                            File.name.ilike(f"%{search}%"),
                            File.original_name.ilike(f"%{search}%"),
                            File.description.ilike(f"%{search}%")
                        )
                    )

                # 查询总数
                count_stmt = select(func.count(File.id))
                if conditions:
                    count_stmt = count_stmt.where(and_(*conditions))

                total_result = await db.execute(count_stmt)
                total = total_result.scalar()

                # 查询数据
                stmt = select(File)
                if conditions:
                    stmt = stmt.where(and_(*conditions))

                stmt = stmt.order_by(desc(File.created_at))
                stmt = stmt.offset((page - 1) * size).limit(size)

                result = await db.execute(stmt)
                files = result.scalars().all()

                logger.info(
                    "获取文件列表成功",
                    page=page,
                    size=size,
                    total=total,
                    actual_count=len(files)
                )

                return {
                    "total": total,
                    "page": page,
                    "size": size,
                    "items": files
                }

        except Exception as e:
            logger.error("获取文件列表失败", error=str(e))
            raise

    async def delete_file(self, file_id: int) -> bool:
        """删除文件"""
        try:
            async with get_db_session() as db:
                file = await db.get(File, file_id)
                if not file:
                    logger.warning("文件不存在，无法删除", file_id=file_id)
                    return False

                # 删除数据库记录
                await db.delete(file)
                await db.commit()

                # 删除物理文件
                filepath = Path(file.file_path)
                if filepath.exists():
                    filepath.unlink()
                    logger.info("物理文件删除成功", filepath=str(filepath))
                else:
                    logger.warning("物理文件不存在", filepath=str(filepath))

                logger.info("文件删除成功", file_id=file_id, filename=file.name)
                return True

        except Exception as e:
            logger.error("文件删除失败", file_id=file_id, error=str(e))
            raise

    async def get_file_content(self, file_id: int) -> Optional[bytes]:
        """获取文件内容"""
        try:
            file = await self.get_file_by_id(file_id)
            if not file:
                return None

            filepath = Path(file.file_path)
            if not filepath.exists():
                logger.error("物理文件不存在", file_id=file_id, filepath=str(filepath))
                return None

            with open(filepath, "rb") as f:
                content = f.read()

            logger.debug("获取文件内容成功", file_id=file_id, size=len(content))
            return content

        except Exception as e:
            logger.error("获取文件内容失败", file_id=file_id, error=str(e))
            raise

    async def update_file(
        self,
        file_id: int,
        target_path: Optional[str] = None,
        description: Optional[str] = None
    ) -> Optional[File]:
        """更新文件信息"""
        try:
            async with get_db_session() as db:
                file = await db.get(File, file_id)
                if not file:
                    return None

                # 更新字段
                if target_path is not None:
                    file.target_path = target_path
                if description is not None:
                    file.description = description

                await db.commit()
                await db.refresh(file)

                logger.info("文件信息更新成功", file_id=file_id)
                return file

        except Exception as e:
            logger.error("文件信息更新失败", file_id=file_id, error=str(e))
            raise

    async def validate_file(self, file: UploadFile) -> Dict[str, Any]:
        """验证上传文件"""
        try:
            errors = []
            warnings = []

            # 检查文件名
            if not file.filename:
                errors.append("文件名不能为空")

            # 检查文件大小
            file.file.seek(0, 2)  # 移动到文件末尾
            file_size = file.file.tell()
            file.file.seek(0)  # 重置到文件开头

            if file_size > settings.max_file_size:
                errors.append(f"文件大小超过限制 ({settings.max_file_size} bytes)")

            if file_size == 0:
                errors.append("文件内容为空")

            # 检查文件类型
            if file.content_type:
                # TODO: 添加更多文件类型验证逻辑
                pass
            else:
                warnings.append("无法确定文件类型")

            return {
                "valid": len(errors) == 0,
                "errors": errors,
                "warnings": warnings,
                "file_size": file_size,
                "content_type": file.content_type
            }

        except Exception as e:
            logger.error("文件验证失败", filename=file.filename, error=str(e))
            return {
                "valid": False,
                "errors": [f"验证过程中发生错误: {str(e)}"],
                "warnings": []
            }

    async def cleanup_orphaned_files(self) -> Dict[str, Any]:
        """清理孤立文件（物理存在但数据库中不存在）"""
        try:
            async with get_db_session() as db:
                # 获取数据库中所有文件路径
                stmt = select(File.file_path)
                result = await db.execute(stmt)
                db_file_paths = set(row[0] for row in result.all())

                # 获取物理目录中的所有文件
                physical_files = set()
                for filepath in self.upload_dir.rglob("*"):
                    if filepath.is_file():
                        physical_files.add(str(filepath))

                # 找出孤立文件
                orphaned_files = physical_files - db_file_paths

                # 删除孤立文件
                deleted_count = 0
                for orphaned_file in orphaned_files:
                    try:
                        os.remove(orphaned_file)
                        deleted_count += 1
                        logger.info("孤立文件删除成功", filepath=orphaned_file)
                    except Exception as e:
                        logger.error("孤立文件删除失败", filepath=orphaned_file, error=str(e))

                logger.info("孤立文件清理完成", deleted_count=deleted_count)

                return {
                    "total_physical_files": len(physical_files),
                    "total_db_files": len(db_file_paths),
                    "orphaned_files": len(orphaned_files),
                    "deleted_files": deleted_count
                }

        except Exception as e:
            logger.error("孤立文件清理失败", error=str(e))
            raise


# 依赖注入函数
def get_file_service() -> FileService:
    """获取文件服务实例"""
    return FileService()


# 数据库会话获取函数
async def get_db_session() -> AsyncSession:
    """获取数据库会话"""
    from database import AsyncSessionLocal
    return AsyncSessionLocal()